from django import forms
from django.utils.translation import ugettext_lazy as _, ugettext
from django.utils.encoding import smart_unicode, force_unicode

from sqlalchemy.orm.util import identity_key
from sqlalchemy.orm.exc import NoResultFound

import logging

class AlchemyForm(forms.BaseForm):
    __metaclass__ = forms.Form.__metaclass__

    def __init__(self, *args, **kwargs):
        self._pop_and_set(kwargs, 'instance')
        self._pop_and_set(kwargs, 'session')
        
        super(AlchemyForm, self).__init__(*args, **kwargs)
        
        if self.instance:
            # Initialize data
            initial = {}
            for key, field in self.fields.items():
                val = getattr(self.instance, key, None)
                if val and isinstance(field, AlchemyModelChoiceField):
                    # We should set primary key's value instead of object itself here
                    logging.error("Instance.val = %s" % val)
                    initial[key] = identity_key(instance=val)[1][0]
                else:
                    initial[key] = val
            kwargs['initial'] = initial
            super(AlchemyForm, self).__init__(*args, **kwargs)
        self.bfields = {}
        for field in self:
            self.bfields.update( { field.name : field } )
    
    def _pop_and_set(self, kwargs, key):
        """
        Pops key value from kwargs and sets into the form.
        """
        if kwargs.has_key(key):
            val = kwargs.pop(key)
        else:
            val = None
        setattr(self, key, val)

    def save(self):
        """
        Checks if form is binded with instance. If so,
        checks if form is valid and then attach validated
        values to this instance. Finally, instance is
        returned.
        """
        if self.instance:
            assert self.is_valid(), "Form has to be valid to perform save() call!"
            cleaned_data = self.cleaned_data
            # We assert that this is SQLAlchemy model instance...
            for key in self.instance._sa_class_manager.keys():
                if cleaned_data.has_key(key):
                    setattr(self.instance, key, cleaned_data[key])
            #self.instance._sa_instance_state.detach()
            return self.instance


class AlchemyModelChoiceIterator(object):
    """
    Based on ModelChoiceIterator from standard django package.
    """
    def __init__(self, field):
        self.field = field
        self.alchemy_query = field.alchemy_query

    def __iter__(self):
        if self.field.empty_label is not None:
            yield (u'', self.field.empty_label)
        for obj in self.alchemy_query.all():
            yield self.choice(obj)

    def choice(self, obj):
        """
        Warning: works only with non-composite primary keys
        as identity_key(instance=instance) returns 2-length
        tuple containing (instance.__class__, (pkeys-tuple) ).
        We will use only first primary key.
        """
        key = identity_key(instance=obj)[1][0]
        return (key, self.field.label_from_instance(obj))

class AlchemyModelChoiceField(forms.ChoiceField):
    """
    Taken from django.forms.ModelChoiceField with
    some little changes to fit with SQLAlchemy models.
    """
    default_error_messages = {
        'invalid_choice': _(u'Select a valid choice. That choice is not one of'
                            u' the available choices.'),
    }

    def __init__(self, alchemy_query, empty_label=u"---------", cache_choices=False,
                 required=True, widget=None, label=None, initial=None,
                 help_text=None, to_field_name=None, *args, **kwargs):
        if required and (initial is not None):
            self.empty_label = None
        else:
            self.empty_label = empty_label
        self.cache_choices = cache_choices

        # Call Field instead of ChoiceField __init__() because we don't need
        # ChoiceField.__init__().
        forms.Field.__init__(self, required, widget, label, initial, help_text,
                       *args, **kwargs)
        self.alchemy_query = alchemy_query
        self.choice_cache = None
        self.to_field_name = to_field_name

    def _get_alchemy_query(self):
        return self._alchemy_query

    def _set_alchemy_query(self, alchemy_query):
        self._alchemy_query = alchemy_query
        self.widget.choices = self.choices

    alchemy_query = property(_get_alchemy_query, _set_alchemy_query)

    # this method will be used to create object labels by the QuerySetIterator.
    # Override it to customize the label.
    def label_from_instance(self, obj):
        """
        This method is used to convert objects into strings; it's used to
        generate the labels for the choices presented by this object. Subclasses
        can override this method to customize the display of the choices.
        """
        return smart_unicode(obj)
    

    def _get_choices(self):
        if hasattr(self, '_choices'):
            return self._choices
        return AlchemyModelChoiceIterator(self)
    
    choices = property(_get_choices, forms.ChoiceField._set_choices)

    def clean(self, value):
        forms.Field.clean(self, value)
        if value is None:
            return None
        instance = self.alchemy_query.get(value)
        if instance is None and self.required:
            raise forms.ValidationError(self.error_messages['invalid_choice'])
        #instance._sa_instance_state.detach()
        return instance


def AlchemyFormFactory(form_name, instance, fields=None, methods=None, session=None):
    """
    Factory method for creating dynamic instance of AlchemyForm.
    Needed in order to inject proper session.
    Mechanizm similar to one used in formalchemy module but
    here we want to base on django.forms only.
    """
    raise NotImplementedError("TODO")


